package prac4;
import java.io.*;

/**
 * Part of a prac to give students experience using linked lists to implement a simple text editor.
 * 
 * @author			Robyn McNamara
 * @author			Leo Li
 * @author			Ian Moriarty
 * @modified		Brendon Taylor (August 2009) - JavaDoc
 *					Maria Garcia de la Banda (March 2011) - details
 *					Leo Li & Ian Moriarty (August 2011) - Expanded to meet the requirements of Prac 4
 * @since			17 March 2009
 * @input			{@link #main(String[])}: command to execute
 * @input			{@link #readFromFile(UnsortedLinkedStringList.ListIterator, String)}: reads a text file into memory
 * @output			{@link #main(String[])}: command response
 * @output			{@link #writeToFile(UnsortedLinkedStringList.ListIterator, String)}: writes from memory to a text file
 * @errorHandling	FileNotFoundException: when a file cannot be found
 * @errorHandling	IOException: when a file cannot be opened, read or written
 * @knownBugs		{@link #main(String[])} and {@link #writeToFile(UnsortedLinkedStringList.ListIterator, String)} are incomplete
 */
public class Prac4 {
	/**
	 * A simple command-line driven text editor.
	 * 
	 * @param		args command-line arguments (not used)
	 * @throws		Exception 
	 * @pre			none
	 * @post		see postconditions for individual methods in this class.
	 * @complexity	best case one could say O(1) if the user immediately exits
	 *				but this is similar to saying Empty. So, assuming a high 
	 *				number of iterations N, the complexity is O(N)*BigOCase. 
	 *				where BigOCase is the complexity of the option selected.
	 */
	public static void main(String[] args) throws Exception {
		InputStreamReader input = new InputStreamReader(System.in);
		BufferedReader keyboard = new BufferedReader(input);
		
		UnsortedLinkedStringList list = new UnsortedLinkedStringList();
		UnsortedLinkedStringList.ListIterator listIt = list.iterator();
		
		boolean quit = false;
		String inputLine = null;
		String[] command;
		
		
		// Command parsing loop
		while (!quit) {
			// Read a command
			try {
				System.out.println("Enter a command:");
				inputLine = keyboard.readLine();
			} catch (IOException e) {
				System.out.println("Error reading from console");
				inputLine = "";
			}
			
			command = inputLine.split(" ");
			listIt.reset();
			
			// Process the command. Also validates for correct number or arguments
			if (command[0].equals("quit") && command.length == 1) {
				quit = true;
			} else if (command[0].equals("read") && command.length == 2) {
				readFromFile(listIt, command[1]);
			} else if (command[0].equals("write") && command.length == 2) {
				writeToFile(listIt, command[1]);
			} else if(command[0].equals("printall") && command.length == 1){
				printAll(listIt);
			} else if(command[0].equals("print") && command.length == 2){
				print(listIt, command[1]);
			} else if(command[0].equals("delete") && command.length == 2) {
				delete(listIt, command[1]);
			} else if(command[0].equals("insert") && command.length == 2){
				insert(keyboard, listIt, command[1]);
			} else if(command[0].equals("display") && command.length == 3){
				display(listIt, command[1], command[2]);
			} else if(command[0].equals("filter")){
				filter(keyboard, command[1]);
			}else {
				System.out.println("Unrecognized command or invalid arguments.");
			}
			
			System.out.println("Done!!");
		}
	}

	/**

	 * Stores each line of an UnsortedLinkedStringList into a file.
	 * 
	 * @param	listIt used to iterate over our linked list 
	 * @param	fileName is the name to be given the output file.
	 * @pre	 	fileName is a valid file name.
	 * @post	every string in every node of the list is written into
	 *			a new file created with name fileName, in the same order
	 *			as it appears in the list. The list itself is not altered.
	 * @complexity	best and worst case: O(N) - N is the number of lines to be written
	 */
	private static void writeToFile(UnsortedLinkedStringList.ListIterator listIt, String fileName) {
		PrintWriter outputWriter;
		// Create an object to do the output.
		try {
			outputWriter = new PrintWriter(new BufferedWriter(new FileWriter(fileName)), true);
		} catch (IOException e) {
			// If opening the file didn't work...
			System.out.println("Error opening file: " + fileName + ".	File not saved.");
			return;
		}				
		
		//Send the lines to the file
		while(listIt.hasNext()){
			try {
				outputWriter.println(listIt.peek());
				listIt.next();
			} catch (Exception e) {
				System.out.println("Cannot peek here");
			}
		}
		
		outputWriter.close();
		
	}

	/**
	 * Reads a text file line by line into an UnsortedLinkedStringList whose 
	 * Iterator is passed in.	If there is any data already in the list, the 
	 * new data is added wherever the iterator is currently pointing to.
	 * 
	 * @param		listIt used to iterate over our linked list ADT.
	 * @param		fileName is the text file to be loaded into memory.
	 * @pre			none
	 * @post		the list contains everything it had before in the same
	 *				order, plus every line in the text file also in the same
	 *				order it appears in the file. The file is not altered.
	 * @complexity	O(N*(M+S)) where 
	 *				N is the number of lines in the list and.
	 *				M is the complexity of reading a line from file.
	 *				S is the complexity of adding a line to the list
	 */
	private static void readFromFile(UnsortedLinkedStringList.ListIterator listIt, String fileName) {
		BufferedReader inputReader;
		
		// Move the listIterator to the end of the list
		try{
			while (listIt.hasNext()) {
				listIt.next();
			}
		} catch (Exception e) {
			// This should never happen due to the check in the while loop
			System.out.println("Error finding end of list");
		}
		
		try {
			// Have a go at opening the file
			inputReader = new BufferedReader(new FileReader(fileName));
		} catch (FileNotFoundException e) {
			System.out.println("File not found: " + fileName);
			return;
		}
		
		// The file is now open, let's read from it.
		String line;
		try {
			while ((line = inputReader.readLine()) != null) {
				listIt.addHere(line);
			}
			inputReader.close();
		} catch (IOException e) {
			// If we got here, then the readLine() died
			System.out.println("Error reading from file: " + fileName);
		}
	}
	
	/**
	 * Prints all lines in the list from start to end.
	 * 
	 * @param		listIt The list iterator object for interfacing with the list
	 * @output		The contents of the list
	 * @complexity	best and worst case: O(N) - N is the length of the list
	 */
	public static void printAll(UnsortedLinkedStringList.ListIterator listIt) {
		try {
			while(listIt.hasNext()){
				System.out.println(listIt.peek());
				listIt.next();
			}
			
		} catch (Exception e) {
			// This should never happen due to the check in the while loop
			System.out.println("Error printing lines");
		}
	}
	
	/**
	 * Prints a specified line from the list.
	 * 
	 * @param		listIt The list iterator object for interfacing with the list
	 * @param		toPrint The line we want to print
	 * @output		The line from the list the user asked to print
	 * @complexity	best case: O(1) - The line to be printed is the first line<br>
	 * 				worst case: O(N) - N is the line number
	 */
	public static void print(UnsortedLinkedStringList.ListIterator listIt, String toPrint) {
		int count = 1;
		int lineNumber;
		
		try {
			// If the argument isn't an integer it's invalid and an error message will be shown
			lineNumber = Integer.parseInt(toPrint);
			
			// If the argument is a line number greater than the length of the list an error message will be shown
			while(count != lineNumber){
				count++;
				listIt.next();	
			}
			System.out.println(listIt.peek());
		} catch (Exception e) {
			System.out.println("Invalid line number");
		}
	}
	
	/**
	 * Deletes a specified line from the list.
	 * 
	 * @param		listIt The list iterator object for interfacing with the list
	 * @param		toDelete The line we want to delete
	 * @post		the line has been deleted from the list
	 * @complexity	best case: O(1) - The line to be deleted is the first line<br>
	 * 				worst case: O(N) - N is the line number
	 */
	public static void delete(UnsortedLinkedStringList.ListIterator listIt, String toDelete) {
		int count = 1;
		int lineNumber;
		
		try {
			// If the argument isn't an integer it's invalid and an error message will be shown
			lineNumber = Integer.parseInt(toDelete);
			
			// If the argument is a line number greater than the length of the list an error message will be shown
			while (count != lineNumber) {
				count++;
				listIt.next();
			}
			
			// This checks we're not one line past the last line as this wouldn't throw an exception in the previous loop.
			if (listIt.peek() == null) {
				throw new Exception();
			}
			
			// Delete the line
			listIt.delete();
		} catch (Exception e) {
			System.out.println("Invalid line number");
		}
	}
	
	/**
	 * Inserts a line of text at a specified point in the list.
	 * 
	 * @param		keyboard The object for the IO interface
	 * @param		listIt The list iterator object for interfacing with the list
	 * @param		insertLocation The location we want to insert the text at
	 * @post		the lines have been inserted into the list at the specified location
	 * @input		the text to be inserted
	 * @output		request for the text to be inserted
	 * @complexity	best case: O(M) - M is the number of lines to be inserted
	 * 				worst case: O(N + M) - N is the line number at which the text is to be inserted and M is the number of lines to be inserted
	 */
	public static void insert(BufferedReader keyboard, UnsortedLinkedStringList.ListIterator listIt, String insertLocation) {
		int count = 1;
		int lineNumber;
		String line;
		
		// Iterate through the list until we get to where we want to insert at
		try {
			lineNumber = Integer.parseInt(insertLocation);
			while(count != lineNumber){
				count++;
				listIt.next();
			}
		} catch (Exception e) {
			System.out.println("Invalid line number");
			return;
		}
		
		// Get the text the user wants inserted and insert it
		try{
			System.out.println("Input insert lines (stop with a full stop on its own line):");
			line = keyboard.readLine();
			while (line.compareTo(".") != 0 ) {
				listIt.addHere(line);
				line = keyboard.readLine();
			}
		} catch (IOException e) {
			System.out.println("IOException. Last line not added.");
		}
	}
	
	/**
	 * Prints the lines in the list from a start point to an end point (inclusive).
	 * 
	 * @param		listIt The list iterator object for interfacing with the list
	 * @param		startPoint The first line to be printed (inclusive)
	 * @param		endPoint The last line to be printed (inclusive)
	 * @output		the lines in the list from startPoint to endPoint (inclusive)
	 * @complexity	O(N) - N is the last line number to be printed
	 */
	public static void display(UnsortedLinkedStringList.ListIterator listIt, String startPoint, String endPoint) {
		int count = 1;
		int start, end;
		
		// Change the start and end points to integers and check that the start point is before the end point
		try {
			start = Integer.parseInt(startPoint);
			end = Integer.parseInt(endPoint);
			
			if(start > end) {
				throw new Exception();
			}
		} catch (Exception e) {
			System.out.println("Invalid arguments");
			return;
		}
		
		try {
			// Iterate through the list to the start node
			while(count != start){
				count++;
				listIt.next();
			}
			
			// Print out the elements until we hit the end node
			for( int i = start; i <= end; i++){
				System.out.println(listIt.peek());
				listIt.next();
			}
		} catch (Exception e) {
			System.out.println("Reached end of list");
		}
	}
	
	/**
	 * Reads in a file then deletes all lines from the list containing a specified String 
	 * 
	 * @param		keyboard The object for the IO interface
	 * @param		listIt The list iterator object for interfacing with the list
	 * @param		word The string we're searching for
	 * @post		all lines containing word have been deleted
	 * @input		the file we're scanning for lines containing word
	 * @output		request for the input
	 * @complexity	best and worst case: O(N*M) - N is the number of lines in the file and M is the complexity of contains for a line of size M
	 */
	public static void filter(BufferedReader keyboard, String word) {
		UnsortedLinkedStringList filterList = new UnsortedLinkedStringList();
		UnsortedLinkedStringList.ListIterator filterListIt = filterList.iterator();
		String fileName;
		int numLinesDeleted = 0;
		
		filterListIt.reset();
		
		// Get the name of the file to be 
		System.out.println("Input the file name you want to edited:");
		try{
			fileName = keyboard.readLine();
		} catch (IOException e) {
			System.out.println("IOException");
			return;
		}
		
		// Read from the file
		readFromFile(filterListIt, fileName);
		filterListIt.reset();
		
		// Check if file is empty
		if (filterList.isEmpty()) {
			// If it's empty either opening the file failed or the file was empty.
			// Either way we don't have to worry about deleting lines.
			// We also don't have to worry about checking which case is true because if it's the former
			// an error will already have been displayed and if it's the latter no notification is necessary.
			System.out.println("0 lines deleted.");
			return;
		}
		
		// See if any lines contain word. If they do, delete them.
		try {
			while(true){
				if (filterListIt.peek().contains(word)) {
					filterListIt.delete();
					numLinesDeleted++;
				} else {
					filterListIt.next();
				}
			}
		} catch (Exception e) {
			// This will always be thrown
			System.out.println("Reached end of file. " + numLinesDeleted + " lines deleted.");
		}
		filterListIt.reset();
		
		// Write the results to the file
		writeToFile(filterListIt, fileName);
	}
}